﻿using MiniFox.Data.Mapping;
using MiniFox.Infrastructure;
using MiniFox.Utility;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data;
using System.Data.Common;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;

namespace MiniFox.Data
{
    /// <summary>
    /// 为.NET 数据库访问提供基础方法
    /// </summary>
    public static class DatabaseUtil
    {
        #region const
        /// <summary>
        /// SELECT COUNT(*) FROM {0} WHERE {1}
        /// </summary>
        public const string SELECT_COUNT = @"SELECT COUNT(1) FROM {0}";
        /// <summary>
        /// SELECT {0} FROM {1}
        /// </summary>
        public const string SELECT_COMMAND = @"SELECT {0} FROM {1}";
        /// <summary>
        ///  "INSERT INTO {0} ({1}) VALUES ({2});"
        /// </summary>
        public const string INSERT_COMMAND = @"INSERT INTO {0} ({1}) VALUES ({2});";
        /// <summary>
        ///  "UPDATE {0} SET {1} WHERE {2};"
        /// </summary>
        public const string UPDATE_COMMAND = @"UPDATE {0} SET {1} WHERE {2};";
        /// <summary>
        /// "DELETE FROM {0} WHERE {1};"
        /// </summary>
        public const string DELETE_COMMAND = @"DELETE FROM {0} WHERE {1};";

        #endregion


        #region 解释排序表达式
        /// <summary>
        /// 排序, 该排序仅用于生成可查询对象(IQueryable)，以便用于后续 linq操作
        /// </summary>
        /// <param name="source">要排序的数据源</param>
        /// <param name="sortField">排序字段</param>
        /// <param name="sortDesc">排序方向</param>
        /// <returns></returns>
        public static IQueryable<T> Sorting<T>(IQueryable<T> source, string sortField, bool sortDesc)
        {
            string sortingDir = "OrderBy";
            if (sortDesc)
                sortingDir = "OrderByDescending";

            PropertyInfo pi = typeof(T).GetProperty(sortField, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

            ParameterExpression param = Expression.Parameter(typeof(T), sortField);
            Type[] types = new Type[2];
            types[0] = typeof(T);
            types[1] = pi.PropertyType;
            Expression expr = Expression.Call(typeof(Queryable), sortingDir, types, source.Expression, Expression.Lambda(Expression.Property(param, pi.Name), param));
            IQueryable<T> query = source.AsQueryable().Provider.CreateQuery<T>(expr);

            return query;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sortFields"></param>
        /// <returns></returns>
        public static string ResolveSortExpression(IDictionary<string, bool> sortFields)
        {
            var sortExpreesion = CreateSortExpression(sortFields);
            if (!string.IsNullOrEmpty(sortExpreesion))
            {
                return $" ORDER BY {sortExpreesion}";
            }
            return string.Empty;
        }

        private static string CreateSortExpression(IDictionary<string, bool> sortFields)
        {
            var sortExpression = string.Empty;

            if (sortFields != null && sortFields.Count > 0)
            {
                var sortExps = new List<string>();
                foreach (var sortItem in sortFields)
                {
                    var sortExp = $"{sortItem.Key} {(sortItem.Value ? "DESC" : "ASC")}";
                    sortExps.Add(sortExp);
                }
                sortExpression = string.Join(",", sortExps);
            }
            return sortExpression;
        }
        #endregion

        #region 解释条件表达式
        /// <summary>
        /// 
        /// </summary>
        /// <param name="exp"></param>
        /// <param name="pExp"></param>
        /// <param name="parameters"></param>
        /// <param name="parameterPrefix"></param>
        /// <returns></returns>
        public static string ResolveConditionExpression(Expression exp, ParameterExpression pExp, ParameterCollection parameters, string parameterPrefix)
        {
            string sql = string.Empty;

            switch (exp.NodeType)
            {
                case ExpressionType.And | ExpressionType.AndAlso:
                    BinaryExpression bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"({ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}) AND ({ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)})";
                    break;
                case ExpressionType.Or | ExpressionType.OrElse:
                    bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"({ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}) OR ({ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)})";
                    break;
                case ExpressionType.Equal:
                    bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"{ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}={ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)}";
                    break;
                case ExpressionType.NotEqual:
                    bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"{ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}!={ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)}";
                    break;
                case ExpressionType.GreaterThan:
                    bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"{ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}>{ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)}";
                    break;
                case ExpressionType.GreaterThanOrEqual:
                    bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"{ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}>={ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)}";
                    break;
                case ExpressionType.LessThan:
                    bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"{ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}<{ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)}";
                    break;
                case ExpressionType.LessThanOrEqual:
                    bExp = exp as BinaryExpression;
                    if (bExp == null)
                        break;
                    sql = $"{ResolveConditionExpression(bExp.Left, pExp, parameters, parameterPrefix)}<={ResolveConditionExpression(bExp.Right, pExp, parameters, parameterPrefix)}";
                    break;
                case ExpressionType.MemberAccess:
                    MemberExpression memberExp = exp as MemberExpression;
                    if (memberExp == null)
                        break;
                    if (memberExp.Expression is ParameterExpression && pExp.Name == ((ParameterExpression)memberExp.Expression).Name)
                    {
                        ColumnAttribute att = Attribute.GetCustomAttribute(memberExp.Member, typeof(ColumnAttribute)) as ColumnAttribute;
                        if (att != null)
                        {
                            sql = att.Name;
                        }
                        else
                        {
                            throw new InvalidOperationException($"MiniFox Error: Cannot match the target column for {pExp.Name}.{memberExp.Member.Name}");
                        }
                    }

                    break;
                case ExpressionType.Call:
                    MethodCallExpression callExp = exp as MethodCallExpression;
                    if (callExp == null || callExp.Method.Name != "Contains" || callExp.Method.Name != "StartsWith" || callExp.Method.Name != "EndsWith")
                        break;
                    string m = ResolveConditionExpression(callExp.Object, pExp, parameters, parameterPrefix);
                    string p = ResolveConditionExpression(callExp.Arguments[0], pExp, parameters, parameterPrefix);
                    switch (callExp.Method.Name)
                    {
                        case "StartsWith":
                            sql = $"{m} LIKE {p}+'%'";
                            break;
                        case "EndsWith":
                            sql = $"{m} LIKE '%'+{p}";
                            break;
                        case "Contains":
                        default:
                            sql = $"{m} LIKE '%'+{p}+'%'";
                            break;
                    }
                    break;
                case ExpressionType.Constant:
                    ConstantExpression constExp = exp as ConstantExpression;
                    if (constExp == null)
                        break;
                    string name = $"P_{parameters.Count + 1}";
                    var par = new Parameter(name);
                    par.ParameterName = $"{parameterPrefix}{name}";
                    par.ClrType = constExp.Type;
                    par.DefaultValue = constExp.Value;
                    par.Nullable = false;
                    par.Direction = ParameterDirection.Input;
                    parameters.Add(par);
                    sql = par.ParameterName;
                    break;
            }

            return sql;
        }
        #endregion

        #region 解释基本select语句主体
        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="schemaName"></param>
        /// <returns></returns>
        public static string ResolveBaseSqlExpression<T>(string schemaName = null)
        {
            ITableMapping tmapping = TableMapping.GetTableMapping<T>();
            string tableName = string.IsNullOrWhiteSpace(schemaName) ? tmapping.SourceTable : $"{schemaName}.{tmapping.SourceTable}";

            List<string> columns = new List<string>();
            foreach (var column in tmapping.ColumnMappings)
            {
                columns.Add(((ColumnMapping)column).SourceColumn);
            }
            string baseSql = string.Format(DatabaseUtil.SELECT_COMMAND, string.Join(", ", columns.ToArray()), tableName);

            return baseSql;
        }

        #endregion

        #region DataTable to ResultSet 基于已完成查询得到结果集的内存行为（包括内存分页）

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <returns></returns>
        public static ViewTable ConvertToViewTable(this DataTable dataTable)
        {
            var viewTable = CreateViewTableSchema(dataTable);
            SetViewTableData(viewTable, dataTable.Select());
            return viewTable;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <returns></returns>
        public static ResultSet GetDataResult(this DataTable dataTable)
        {
            return GetDataResult(dataTable, string.Empty, string.Empty);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="sortFields"></param>
        /// <param name="filterExpression"></param>
        /// <returns></returns>
        public static ResultSet GetDataResult(this DataTable dataTable, IDictionary<string, bool> sortFields, string filterExpression)
        {
            var sortExpression = CreateSortExpression(sortFields);
            return GetDataResult(dataTable, sortExpression, filterExpression);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="sortExpression"></param>
        /// <param name="filterExpression"></param>
        /// <returns></returns>
        public static ResultSet GetDataResult(this DataTable dataTable, string sortExpression, string filterExpression)
        {
            var dataRows = dataTable.Select(filterExpression, sortExpression);
            var result = new ResultSet();
            //SetResult(dataTable, result);
            SetResultData(result, dataRows, 0, dataRows.Length - 1);
            return result;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <returns></returns>
        public static PagedResultSet GetDataResult(this DataTable dataTable, int pageIndex, int pageSize)
        {
            return GetDataResult(dataTable, pageIndex, pageSize, string.Empty, string.Empty);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="sortFields"></param>
        /// <param name="filterExpression"></param>
        /// <returns></returns>
        public static PagedResultSet GetDataResult(this DataTable dataTable, int pageIndex, int pageSize, IDictionary<string, bool> sortFields, string filterExpression)
        {
            var sortExpression = CreateSortExpression(sortFields);
            return GetDataResult(dataTable, pageIndex, pageSize, sortExpression, filterExpression);
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="pageIndex"></param>
        /// <param name="pageSize"></param>
        /// <param name="sortExpression"></param>
        /// <param name="filterExpression"></param>
        /// <returns></returns>
        public static PagedResultSet GetDataResult(this DataTable dataTable, int pageIndex, int pageSize, string sortExpression, string filterExpression)
        {
            var dataRows = dataTable.Select(filterExpression, sortExpression);
            var result = new PagedResultSet(pageIndex, pageSize);
            //SetResult(dataTable, result);
            SetResultData(result, dataRows, result.StartRowIndex, result.EndRowIndex);
            return result;
        }
        //private static void SetResult(this DataTable dataTable, ResultSet result)
        //{
        //    foreach (DataColumn column in dataTable.Columns)
        //    {
        //        result.Columns.Add(column.ColumnName, Type.GetTypeCode(column.DataType));
        //    }
        //}
        private static void SetResultData(ResultSet result, DataRow[] dataRows, int startRowIndex, int endRowIndex)
        {
            var list = new List<IDictionary<string, object>>();
            for (var i = startRowIndex; i <= endRowIndex && i < dataRows.Length; i++)
            {
                var dataRow = dataRows[i];
                IDictionary<string, object> data = dataRow.ConvertToDictionary();
                list.Add(data);
            }
            result.RowCount = dataRows.Length;
            result.Data = list;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <returns></returns>
        public static ViewTable CreateViewTableSchema(DataTable dataTable)
        {
            ViewTable viewTable = new ViewTable();
            foreach(DataColumn dataColumn in dataTable.Columns)
            {
                ViewColumn viewColumn = dataColumn;
                viewTable.Columns.Add(viewColumn);
            }
            return viewTable;
        }
        private static void SetViewTableData(ViewTable viewTable, DataRow[] dataRows)
        {
            foreach (var dataRow in dataRows)
            {
                var viewRow = viewTable.NewRow();
                foreach (ViewColumn viewColumn in viewTable.Columns)
                {
                    viewRow[viewColumn.Name] = dataRow[viewColumn.Name];
                }
                viewTable.Rows.Add(viewRow);
            }
        }
        private static void SetViewTableData(ViewTable viewTable, DataRow[] dataRows, int startRowIndex, int endRowIndex)
        {
            for (var i = startRowIndex; i <= endRowIndex && i < dataRows.Length; i++)
            {
                var dataRow = dataRows[i];
                var viewRow = viewTable.NewRow();
                foreach(ViewColumn viewColumn in viewTable.Columns)
                {
                    viewRow[viewColumn.Name] = dataRow[viewColumn.Name];
                }
                viewTable.Rows.Add(viewRow);
            }
        }

        private static DataRow[] GetDataRows(this DataTable dataTable, IQueryObject queryRequest)
        {
            if (queryRequest == null)
                return dataTable.Select();
            var filterExpression = queryRequest.GetQueryExpression();
            var sortExpression = CreateSortExpression(queryRequest.SortFields);

            var dataRows = dataTable.Select(filterExpression, sortExpression);
            return dataRows;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="queryRequest"></param>
        /// <returns></returns>
        public static ResultObject<ViewTable> GetDataResult(this DataTable dataTable, IQueryObject queryRequest)
        {
            var viewTable = CreateViewTableSchema(dataTable);
            var dataRows = GetDataRows(dataTable, queryRequest);
            SetViewTableData(viewTable, dataRows, 0, dataRows.Length - 1);
            var resultObject = new ResultObject<ViewTable>(dataRows.Length);
            resultObject.Result = viewTable;
            return resultObject;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="queryRequest"></param>
        /// <returns></returns>
        public static PagedResultObject<ViewTable> GetDataResult(this DataTable dataTable, IPagedQueryObject queryRequest)
        {
            var viewTable = CreateViewTableSchema(dataTable);
            var dataRows = GetDataRows(dataTable, queryRequest);
            var resultObject = new PagedResultObject<ViewTable>(dataRows.Length, queryRequest.PageIndex, queryRequest.PageSize);
            int startRowIndex = Tools.GetStartIndex(resultObject.PageSize, resultObject.PageIndex);
            int endRowIndex = Tools.GetEndIndex(resultObject.PageSize, resultObject.PageIndex, resultObject.RowCount);
            SetViewTableData(viewTable, dataRows, startRowIndex, endRowIndex);
            resultObject.Result = viewTable;
            return resultObject;
        }

        #endregion

        #region XDataTable to ResultRowSet

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <returns></returns>
        //public static ResultSet GetDataResult(this XDataTable dataTable)
        //{
        //    return GetDataResult(dataTable, string.Empty, string.Empty);
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="queryRequest"></param>
        ///// <returns></returns>
        //public static ResultSet GetDataRowSet(this XDataTable dataTable, QueryObject queryRequest)
        //{
        //    var filterExpression = queryRequest.Parameters.GetQueryExpression();
        //    return GetDataResult(dataTable, queryRequest.SortFields, filterExpression);
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="sortFields"></param>
        ///// <param name="filterExpression"></param>
        ///// <returns></returns>
        //public static ResultSet GetDataResult(this XDataTable dataTable, IDictionary<string, bool> sortFields, string filterExpression)
        //{
        //    var sortExpression = CreateSortExpression(sortFields);
        //    return GetDataResult(dataTable, sortExpression, filterExpression);
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="sortExpression"></param>
        ///// <param name="filterExpression"></param>
        ///// <returns></returns>
        //public static ResultSet GetDataResult(this XDataTable dataTable, string sortExpression, string filterExpression)
        //{
        //    var dataRows = dataTable.Select(filterExpression, sortExpression);
        //    var result = new ResultSet();
        //    dataTable.SetResult(result);
        //    var list = new List<RowItem>();
        //    //int rowIndex = 0;
        //    foreach (var r in dataRows)
        //    {
        //        var dataRow = (XDataRow)r;
        //        list.Add(dataRow);
        //    }
        //    result.Data = list;
        //    return result;
        //}
        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="pageSize"></param>
        ///// <param name="pageIndex"></param>
        ///// <returns></returns>
        //public static PagedResultSet GetPagedResult(this XDataTable dataTable, int pageSize, int pageIndex)
        //{
        //    return GetPagedResult(dataTable, pageSize, pageIndex, string.Empty, string.Empty);
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="pagedQuery"></param>
        ///// <returns></returns>
        //public static PagedResultSet GetPagedResult(this XDataTable dataTable, PagedQueryObject pagedQuery)
        //{
        //    var pageIndex = pagedQuery.PageIndex;
        //    var pageSize = pagedQuery.PageSize;
        //    var filterExpression = pagedQuery.Parameters.GetQueryExpression();
        //    return GetPagedResult(dataTable, pageIndex, pageSize, pagedQuery.SortFields, filterExpression);
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="pageIndex"></param>
        ///// <param name="pageSize"></param>
        ///// <param name="sortFields"></param>
        ///// <param name="filterExpression"></param>
        ///// <returns></returns>
        //public static PagedResultSet GetPagedResult(this XDataTable dataTable, int pageIndex, int pageSize, IDictionary<string, bool> sortFields, string filterExpression)
        //{
        //    var sortExpression = string.Empty;
        //    if (sortFields != null && sortFields.Count > 0)
        //    {
        //        var sortExps = new List<string>();
        //        foreach (var sortItem in sortFields)
        //        {
        //            var sortExp = $"{sortItem.Key} {(sortItem.Value ? "DESC" : "ASC")}";
        //            sortExps.Add(sortExp);
        //        }
        //        sortExpression = string.Join(",", sortExps);
        //    }
        //    return GetPagedResult(dataTable, pageIndex, pageSize, sortExpression, filterExpression);
        //}

        ///// <summary>
        ///// 
        ///// </summary>
        ///// <param name="dataTable"></param>
        ///// <param name="pageIndex"></param>
        ///// <param name="pageSize"></param>
        ///// <param name="sortExpression"></param>
        ///// <param name="filterExpression"></param>
        ///// <returns></returns>
        //public static PagedResultSet GetPagedResult(this XDataTable dataTable, int pageIndex, int pageSize, string sortExpression, string filterExpression)
        //{
        //    var dataRows = dataTable.Select(filterExpression, sortExpression);
        //    var result = new PagedResultSet(pageIndex, pageSize);
        //    foreach (DataColumn column in dataTable.Columns)
        //    {
        //        result.Columns.Add(column.ColumnName, Type.GetTypeCode(column.DataType));
        //    }
        //    var list = new List<RowItem>();
        //    for (var i = result.StartRowIndex; i <= result.EndRowIndex; i++)
        //    {
        //        var dataRow = (XDataRow)dataRows[i];
        //        list.Add(dataRow);
        //    }
        //    result.Data = list;
        //    return result;
        //}

        #endregion

        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataRow"></param>
        /// <returns></returns>
        public static IDictionary<string, object> ConvertToDictionary(this DataRow dataRow)
        {
            var dataTable = dataRow.Table;
            var dict = new Dictionary<string, object>();
            foreach (DataColumn dataCol in dataTable.Columns)
            {
                dict.Add(dataCol.ColumnName, dataRow[dataCol.ColumnName]);
            }
            return dict;
        }
        /// <summary>
        /// 
        /// </summary>
        /// <param name="dataTable"></param>
        /// <param name="nodeKey"></param>
        /// <param name="superKey"></param>
        /// <param name="sortKey"></param>
        /// <param name="groupKey"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public static IEnumerable<ITreeNode> GetTreeNodes(this DataTable dataTable, string nodeKey, string superKey, string sortKey, string groupKey)
        {
            if (!dataTable.Columns.Contains(nodeKey) || !dataTable.Columns.Contains(superKey) || !dataTable.Columns.Contains(groupKey))
            {
                throw new ArgumentNullException("nodeKey|nodeSuperKey|groupKey");
            }

            var resultObject = new TreeTableResultObject(dataTable, nodeKey, superKey, sortKey, groupKey);
            return resultObject.Result;
        }

        #region lambda
        /// <summary>
        /// 
        /// </summary>
        /// <param name="source"></param>
        /// <param name="sortFields"></param>
        /// <returns></returns>
        public static IQueryable<TEntity> DataSorting<TEntity>(this IQueryable<TEntity> source, IDictionary<string, bool> sortFields)
        {
            var i = 0;
            foreach (KeyValuePair<string, bool> pair in sortFields)
            {
                string sortField = pair.Key;
                bool sortDesc = pair.Value;
                string sortingDir;

                if (i <= 0)
                {
                    sortingDir = "OrderBy";
                    if (sortDesc)
                        sortingDir = "OrderByDescending";
                }
                else
                {
                    sortingDir = "ThenBy";
                    if (sortDesc)
                        sortingDir = "ThenByDescending";

                }

                PropertyInfo pi = typeof(TEntity).GetProperty(sortField, BindingFlags.Public | BindingFlags.Instance | BindingFlags.IgnoreCase);

                ParameterExpression param = Expression.Parameter(typeof(TEntity), sortField);
                Type[] types = new Type[2];
                types[0] = typeof(TEntity);
                types[1] = pi.PropertyType;
                Expression expr = Expression.Call(typeof(Queryable), sortingDir, types, source.Expression, Expression.Lambda(Expression.Property(param, pi.Name), param));
                var query = source.Provider.CreateQuery<TEntity>(expr);
                //Enumerable<T> ienum = query;
                source = query;
                i++;
            }
            return source;
        }
        #endregion
    }
}
